#include <stdio.h>
#include <dos.h>
#include "R5.H"
#include "mpx_supt.h"

struct dcb{
	int opened; //OPEN or CLOSED
	int *flags; //pointer to event flags
	int status; //IDLE or READING or WRITING
	
	char *in_buff; //pointer to the requestor's input buffer
	int *in_count; //counter of the maximum number of chars that can be placed in the buffer
	int in_done; //counter for how many characters have been placed in the requester's buffer
	
	char *out_buff; 
	int *out_count; 
	int out_done; 
	
	char ring_buffer[1024];
	int ring_buffer_write; //index where you will write the next character to the ring buffer
	int ring_buffer_read; //index where you will remove the next character from the ring buffer
	int ring_buffer_count; //count of how manhy characters are stored but not read from the buffer
}dcb;

void interrupt (*oldfunc) (void);
int code;

int com_open(int *eflag_p, int baud_rate){
long baud_rate_div;
int mask;
/*
Check Parameters
Set device to OPEN
Save event flag
Set device to IDLE
Initialize the DCB ring buffer variables
Save the old interrupt handler
Install the new handler
Compute the Baud rate divisor
Store 0x80 in line control register
Set baud rate divisor in the registers
Store 0x03 in the line control register
Enable the appropiate level int eh PIC Mask register
store 0x08 in the modem control register
Store 0x01 in the interrupt enable register
*/
	if(eflag_p == NULL){
		return R5_ERR_EVENT_FLAG;
	}
	else if(baud_rate < 0){
		return R5_ERR_BAUD_RATE;
	}
	else if(dcb.opened == OPEN){
		return R5_ERR_PORT_OPEN;
	}
	else{ //everything good
		dcb.opened = OPEN;
		dcb.flags = eflag_p;
		dcb.status = IDLE;
		dcb.ring_buffer_write = 0;
		dcb.ring_buffer_read = 0;
		dcb.ring_buffer_count = 0;
		oldfunc = getvect(COM1_INT_ID);
		setvect(COM1_INT_ID, &io);
		baud_rate_div = 115200 / (long) baud_rate;
		outportb(COM1_LC, 0x80);  //enables writing for baud rate
		outportb(COM1_BRD_LSB, baud_rate_div&0xFF); //sets the lowest byte
		outportb(COM1_BRD_MSB, (baud_rate_div >> 8)&0xFF); //sets the highest byte
		outportb(COM1_LC, 0x03); 
		//ENABLE APPROPIATE LEVEL FOR PIC MASK REGISTER next 5 lines
		disable();
		mask = inportb(PIC_MASK);
		mask = mask & ~0x10; //I think it should be ~0x10 or 0xEF to be bit 4, the bit that is for COM1
		outportb(PIC_MASK, mask);
		enable();
		
		outportb(COM1_MC, 0x08); //enable serial interrupts
		outportb(COM1_INT_EN, 0x01);//enable input ready interrupts
		return 0;
	}
}



int com_close(){
int mask;
/*
Check that the port is open (return error)
Close the DCB
Disable the appropiate level in the PIC mack register
Load 0x00 into the modem status and interrupt enable registers
Restore the original interrupt vector
*/
	if(dcb.opened != OPEN){
		return R5_ERR_NOT_OPEN;
	}
	else{
		dcb.opened = CLOSED;
		//disable appropiate level for PIC mask register
		disable();
		mask = inportb(PIC_MASK);
		mask = mask | 0x10; //I think it should be 0x10 to be bit 4, the bit that is for COM1
		outportb(PIC_MASK, mask);
		enable();
		
		outportb(COM1_MS, 0x00);
		outportb(COM1_INT_EN, 0x00);
		setvect(COM1_INT_ID, oldfunc);
		return 0;
	}
}
int com_write(char *buf_p, int *count_p){
/*
Check parameters (return error)
Intall the output buffer in the DCB
Set status to WRITING
Clear the event flag
Write the first char to the com port
Advance the output buffer
Enable write interrupts
Return 0
*/
	int mask;
	if(dcb.opened != OPEN){
		return R5_ERR_CLOSED;
	}
	else if(dcb.status != IDLE){
		return R5_ERR_BUSY;
	}
	else if(buf_p == NULL){
		return R5_ERR_BUFF;
	}
	else if(count_p == NULL){
		return R5_ERR_COUNT;
	}
	else{
		dcb.out_buff = buf_p;
		dcb.out_count = count_p;
		dcb.out_done = 0;
		dcb.status = WRITING;
		*(dcb.flags) = 0;//clear flags //changed 0 to 1
		outportb(COM1_BASE, *dcb.out_buff);
		dcb.out_done++;
		dcb.out_buff++;
		
		disable();
		mask = inportb(COM1_INT_EN);
		mask = mask | 0x02;
		outportb(COM1_INT_EN, mask);
		enable();
		
		//*count_p = dcb.out_done;//manual says, but maybe not needed? count is always one
		return 0;
	}
}
int com_read(char *buf_p, int *count_p){
/*
Check Parameters OPEN and IDLE ( return error)
Initialize input buffer
Set input done count to 0
Clear the event flag
Disable interrupts
Set device to READING
Read characters till buffer empty, space full or a '\r'
Re-enable interrupts
If space not full, keep status at READING and return 0

NULL terminate the input buffer with '\0'
Set status to IDLE
return count of characters placed in the input buffer
*/
	
	if(dcb.opened != OPEN){
		return R5_ERR_PORT_CLOSED;
	}
	else if(dcb.status != IDLE){
		return R5_ERR_DEV_BUSY;
	}
	else if(buf_p == NULL) {
		return R5_ERR_INV_BUF_ADD;
	}
	else if(count_p == NULL) {
		return R5_ERR_INV_CNT_ADD;
	}
	else {
		dcb.in_buff = buf_p;
		dcb.in_count = count_p;
		dcb.in_done = 0;
		*(dcb.flags) = 0;
		
		disable();
		dcb.status = READING;
		
		while (dcb.ring_buffer_count > 0 && dcb.in_done < *dcb.in_count && dcb.ring_buffer[dcb.ring_buffer_read] != '\r') {
			*dcb.in_buff = dcb.ring_buffer[dcb.ring_buffer_read];
			dcb.ring_buffer_read++;
			dcb.in_done++;
			dcb.in_buff++;//advance input buffer to next element
			dcb.ring_buffer_count--;
		}
		enable();
		
		if (dcb.in_done < dcb.in_buff) {//requesting buffer not full
			return 0;
		}
		else{
			*dcb.in_buff = '\0';
			dcb.status = IDLE;
			*(dcb.flags) = 1;//completion
			*count_p = dcb.in_done;
			return 0;
		}
	}
	
}

void interrupt io(){
/*
if port not open, send EOI code to PIC command register and return
inport b from Interrupt ID register to determine type of interrupt
if the register & 0x07 == 2 -> write, if 4 -> read
send EOI to PIC command register
*/
	if(dcb.opened != OPEN){
		outportb(PIC_CMD, EOI);
	}
	else{
		code = inportb(COM1_INT_ID_REG);  
		code = code & 0x07;
		if(code == 2){
			writeio();
		}
		else if(code == 4){
			readio();
		}
		else{
			//unknown interrupt
		}
		outportb(PIC_CMD, EOI);
	}
}

//second level interrupt
void readio(){
/*
read a character from input register
is status not READING, add to the ring buffer and increment index
if buffer full, discard character and return
status is READING store character in input buffer and increment count
if input buffer full or character is '\r', null terminate'\0' the buffer, set device IDLE, set event flag, input count = input done
*/
	char in;
	in = (char)(inportb(COM1_BASE));//is this correct port ID? //Credits to Forrest, for COM1_BASE, had no idea
	if (dcb.status != READING) {
		if (dcb.ring_buffer_count < R5_RING_BUFF_SIZE) {
			dcb.ring_buffer[dcb.ring_buffer_write] = in;
			dcb.ring_buffer_write++;
			dcb.ring_buffer_count++;
		}
		//returns to upper level handler here
	}
	else {//current status is READING
		*dcb.in_buff = in;
		dcb.in_buff++;
		dcb.in_done++;
		if(dcb.in_done >= *dcb.in_count || in == '\r'){
			*dcb.in_buff = '\0';
			dcb.status = IDLE;
			*dcb.flags = 1;
			*dcb.in_count = dcb.in_done;
		}
	}
}

//second level interrupt
void writeio(){
int mask;
/*
if not WRITING, ignore and return
if output not completely copied, copy character and return
otherwise, set status to IDLE, set event flag, disable interrupts while clearing bit one in the interrupt enable register
*/
	if(dcb.status != WRITING){
		//do nothing
	}
	else if(dcb.out_done < *dcb.out_count){
		outportb(COM1_BASE, *dcb.out_buff);
		dcb.out_done++;
		dcb.out_buff++;
	}
	else{ //output is done
		dcb.status = IDLE;
		*dcb.flags = 1;
		disable();
		mask = inportb(COM1_INT_EN);
		mask = mask & ~0x02;
		outportb(COM1_INT_EN, mask);
		enable();
	}
}